/*
异步编程：使用 stream
https://dart.cn/tutorials/language/streams


Stream 提供一个异步的数据序列。

数据序列包括用户生成的事件和从文件读取的数据。

你可以使用 Stream API 中的 listen() 方法和 await for 关键字来处理一个 Stream。

当出现错误时，Stream 提供一种处理错误的方式。

Stream 有两种类型：Single-Subscription 和 Broadcast。

Future 表示一个不会立即完成的计算过程。与普通函数直接返回结果不同的是异步函数返回一个将会包含结果的 Future。该 Future 会在结果准备好时通知调用者。

Stream 是一系列异步事件的序列。其类似于一个异步的 Iterable，不同的是当你向 Iterable 获取下一个事件时它会立即给你，但是 Stream 则不会立即给你而是在它准备好时告诉你。

接收 Stream 事件
Stream 可以通过许多方式创建，这个话题我们会在另一篇文章详述，而这些所有的创建方式都可以相同的方式在代码中使用：
像使用 for 循环 迭代一个 Iterable 一样，我们可以使用 异步 for 循环 （通常我们直接称之为 await for）来迭代 Stream 中的事件。例如：

 */

/*
  本页面内嵌了一些 DartPads 做例子展示，

如果你只看到了空白的框框（而没有任何内容），请查阅 DartPad 常见问题页面。

https://dart.cn/tools/dartpad/troubleshoot




Dart 中的 async 和 async* 有什么区别？
https://www.likecs.com/ask-199968.html
将函数标记为async 或async* 允许它使用async/await 作为Future。

两者之间的区别在于async* 将始终返回Stream 并提供一些语法糖以通过yield 关键字发出值。


async 给你一个Future

async* 给你一个Stream

  异步
   您将async 关键字添加到执行某些可能需要很长时间的工作的函数中。它返回包装在Future 中的结果。

  异步*
   您添加 async* 关键字来创建一个函数，该函数一次返回一堆未来值。结果被封装在一个 Stream 中。


 */

Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for (final value in stream) {
    sum += value;
  }

  return sum;
}

/*
  该代码只是简单地接收整型事件流中的每一个事件并将它们相加，然后返回（被 Future 包裹）相加后的整型值。当循环体结束时，函数会暂停直到下一个事件到达或 Stream 完成。

  内部使用 await for 循环的函数需要使用 async 关键字标记。


  下面的示例中使用了 async* 函数生成一个简单的整型 Stream 来测试上一个代码片段：

 */

Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    yield i;
  }
}

//此函数每秒发出一个值，每次递增。
Stream<int> foo() async* {
  for (int k = 0; k < 5; k++) {
    await Future.delayed(const Duration(seconds: 1)); // 堵塞当前线程
    yield k;
  }
}


/*
  Stream 的使用
  Stream 类中包含了许多像 Iterable 类中一样的辅助方法帮助你实现一些常用的操作。例如，你可以使用 Stream API 中的 lastWhere() 方法从 Stream 中找出最后一个正整数。

 */
Future<int> lastPositive(Stream<int> stream) =>
    stream.lastWhere((x) => x >= 0);

void main() async {
  var stream = countStream(10);
  var sum = await sumStream(stream);
  print('累加结果 sum = $sum');

  // var stream2 = foo();
  // var sum2 = await sumStream(stream2);
  // print('累加结果 sum2 = $sum2');

  print('---------------  错误事件  -----------------------------------------  ');

  var stream3 = countStream2(10);

  var sum3 = await sumStream2(stream3);
  print('错误事件 sum3 = $sum3');
  //错误事件 sum3 = -1
}

/*

    错误事件
    当 Stream 再也没有需要处理的事件时会变为完成状态，与此同时，调用者可以像接收到新事件回调那样接收 Stream 完成的事件回调。当使用 await for 循环读取事件时，循环会在 Stream 完成时停止。

    有时在 Stream 完成前会出现错误；比如从远程服务器获取文件时出现网络请求失败，或者创建事件时出现 bug，尽管错误总是会有可能存在，但它出现时应该告知使用者。

    Stream 可以像提供数据事件那样提供错误事件。大多数 Stream 会在第一次错误出现后停止，但其也可以提供多次错误并可以在在出现错误后继续提供数据事件。在本篇文档中我们只讨论 Stream 最多出现并提供一次错误事件的情况。

    当使用 await for 读取 Stream 时，如果出现错误，则由循环语句抛出，同时循环结束。你可以使用 try-catch 语句捕获错误。下面的示例会在循环迭代到参数值等于 4 时抛出一个错误：

 */

Future<int> sumStream2(Stream<int> stream) async {
  var sum = 0;
  try {
    await for (final value in stream) {
      sum += value;
    }
  } catch (e) {
    return -1;
  }

  return sum;
}

Stream<int> countStream2(int to) async* {
  for (int i = 1; i <= to; i++) {
    if (i == 4) {
      throw Exception('Intentional exception');
    } else {
      yield i;
    }
  }
}
